package com.hys.app.service.erp.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hys.app.converter.erp.ProductStockConverter;
import com.hys.app.framework.database.WebPage;
import com.hys.app.framework.exception.ServiceException;
import com.hys.app.framework.database.mybatisplus.base.BaseServiceImpl;
import com.hys.app.framework.util.PageConvert;
import com.hys.app.mapper.erp.ProductStockMapper;
import com.hys.app.model.erp.dos.ProductDO;
import com.hys.app.model.erp.dos.ProductStockDO;
import com.hys.app.model.erp.dto.ProductStockQueryParams;
import com.hys.app.model.erp.enums.StockOperateEnum;
import com.hys.app.model.erp.vo.ProductStockVO;
import com.hys.app.model.erp.vo.ProductStockWarningVO;
import com.hys.app.model.erp.vo.WarehouseVO;
import com.hys.app.service.erp.ProductManager;
import com.hys.app.service.erp.ProductStockManager;
import com.hys.app.service.erp.WarehouseManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 商品库存业务层实现
 *
 * @author 张崧
 * @since 2023-12-07 10:08:44
 */
@Service
public class ProductStockManagerImpl extends BaseServiceImpl<ProductStockMapper, ProductStockDO> implements ProductStockManager {

    @Autowired
    private ProductStockConverter converter;

    @Autowired
    private WarehouseManager warehouseManager;

    @Autowired
    private ProductManager productManager;

    @Autowired
    private ProductStockMapper productStockMapper;

    @Override
    public WebPage<ProductStockVO> list(ProductStockQueryParams queryParams) {
        WebPage<ProductStockDO> webPage = baseMapper.selectPage(queryParams);
        return converter.convert(webPage);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void initStockByProduct(List<Long> productIds, Long goodsId) {
        List<WarehouseVO> warehouseList = warehouseManager.listAll(null);

        List<ProductStockDO> list = new ArrayList<>();
        for (WarehouseVO warehouseVO : warehouseList) {
            for (Long productId : productIds) {
                list.add(new ProductStockDO(warehouseVO.getId(), goodsId, productId, 0));
            }
        }

        super.saveBatch(list);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void initStockByWarehouse(Long warehouseId) {
        Long lastId = 0L;
        while (true) {
            List<ProductDO> productList = productManager.lambdaQuery().gt(ProductDO::getId, lastId).last("limit 1000").list();
            if (productList.isEmpty()) {
                break;
            }
            lastId = productList.get(productList.size() - 1).getId();

            List<ProductStockDO> stockList = productList.stream()
                    .map(productDO -> new ProductStockDO(warehouseId, productDO.getGoodsId(), productDO.getId(), 0))
                    .collect(Collectors.toList());
            super.saveBatch(stockList);
        }
    }

    @Override
    public void deleteStockByProduct(List<Long> productIds) {
        // 目前注释掉下面的代码，因为产品被删除后，暂时先不需要删除该产品的库存，因为比如入库单退货时要扣批次的库存，批次库存和产品库存应该是保持一致的


//        if(productIds.isEmpty()){
//            return;
//        }

//        lambdaUpdate().in(ProductStockDO::getProductId, productIds).remove();
    }

    @Override
    public void deleteStockByWarehouse(Long warehouseId) {
        // 目前注释掉下面的代码，暂时先不需要删除产品库存数据，因为比如入库单退货时要扣批次的库存，批次库存和产品库存应该是保持一致的
//        lambdaUpdate().eq(ProductStockDO::getWarehouseId, warehouseId).remove();
    }

    /**
     * 查询商品库存预警分页列表数据
     *
     * @param pageNo      分页数
     * @param pageSize    每页数量
     * @param warehouseId 仓库ID
     * @param deptId      部门ID
     * @param name        商品名称
     * @return
     */
    @Override
    public WebPage<ProductStockWarningVO> listWarningStock(Long pageNo, Long pageSize, Long warehouseId, Long deptId, String name) {
        IPage<ProductStockWarningVO> iPage = this.productStockMapper.listWarningStockPage(new Page(pageNo, pageSize), warehouseId, deptId, name);
        return PageConvert.convert(iPage);
    }

    /**
     * 获取某个仓库中某个商品的库存信息
     *
     * @param productId   产品ID
     * @param warehouseId 仓库ID
     * @return
     */
    @Override
    public ProductStockDO getStock(Long productId, Long warehouseId) {
        return this.lambdaQuery()
                .eq(ProductStockDO::getProductId, productId)
                .eq(ProductStockDO::getWarehouseId, warehouseId)
                .one();
    }

    @Override
    public List<ProductStockDO> listByWarehouseAndProduct(List<Long> warehouseIds, List<Long> productIds) {
        if (CollUtil.isEmpty(productIds)) {
            return Collections.emptyList();
        }
        return lambdaQuery()
                .in(warehouseIds != null, ProductStockDO::getWarehouseId, warehouseIds)
                .in(ProductStockDO::getProductId, productIds)
                .list();
    }

    @Override
    public Map<Long, Integer> queryStockNum(List<Long> warehouseIds, List<Long> productIds) {
        // 批量查询库存
        List<ProductStockDO> productStockList = listByWarehouseAndProduct(warehouseIds, productIds);
        // 按产品分组
        Map<Long, List<ProductStockDO>> groupByProduct = productStockList.stream().collect(Collectors.groupingBy(ProductStockDO::getProductId));
        // 循环每个产品，计算产品的总库存
        return productIds.stream().collect(Collectors.toMap(Function.identity(), productId -> {
            List<ProductStockDO> stockList = groupByProduct.get(productId);
            return stockList == null ? 0 : stockList.stream().mapToInt(ProductStockDO::getNum).sum();
        }));
    }

    /**
     * 该方法不应该单独调用，应该在更新批次是调用
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStock(Long warehouseId, Long productId, StockOperateEnum operate, int changeNum) {
        if (changeNum <= 0) {
            throw new ServiceException("库存变更数量必须大于0");
        }

        boolean update = lambdaUpdate()
                .setSql("num = num + " + (operate == StockOperateEnum.Increase ? changeNum : -changeNum))
                .eq(ProductStockDO::getWarehouseId, warehouseId)
                .eq(ProductStockDO::getProductId, productId)
                // 如果是扣减库存，需要校验剩余库存是否充足
                .ge(operate == StockOperateEnum.Reduce, ProductStockDO::getNum, changeNum)
                .update();

        if (!update) {
            // 因为都是基于批次来更新仓库库存，只要批次校验没问题，这里就没问题，走到这里应该是代码有bug
            throw new ServiceException("库存更新失败，请联系管理员排查：" + warehouseId + "_" + productId);
        }
    }
}

